/*
 * Copyright (c) 2010, Jim Hollinger
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of Jim Hollinger nor the names of its contributors
 *     may be used to endorse or promote products derived from this
 *     software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <string>

#include "debughelper/debughelper.h"
#include "kmlreader.h"

#ifdef XML_LARGE_SIZE
#  if defined(XML_USE_MSC_EXTENSIONS) && _MSC_VER < 1400
#    define XML_FMT_INT_MOD "I64"
#  else
#    define XML_FMT_INT_MOD "ll"
#  endif
#else
#  define XML_FMT_INT_MOD "l"
#endif


using namespace kmlio;


void XMLCALL kmlio::xml_startElementHandler(void *userData,
                                            const char *name,
                                            const char **atts) {
    if (userData != NULL) {
        KmlReader *reader = reinterpret_cast<KmlReader *> (userData);
        reader->startElementHandler(name, atts);
    }
}

void XMLCALL kmlio::xml_endElementHandler(void *userData,
                                          const char *name) {
    if (userData != NULL) {
        KmlReader *reader = reinterpret_cast<KmlReader *> (userData);
        reader->endElementHandler(name);
    }
}

void XMLCALL kmlio::xml_characterDataHandler(void *userData,
                                             const XML_Char *s,
                                             int len) {
    if (userData != NULL) {
        KmlReader *reader = reinterpret_cast<KmlReader *> (userData);
        reader->characterDataHandler(s, len);
    }
}

const int KmlReader::MAX_BUFFER_LEN = 16384;

/**
 ****************************************************************************
 * @brief  Constructor
 ****************************************************************************
**/
KmlReader::KmlReader()
    : parser(NULL), buffer(NULL), buffer2(NULL), buffer2len(0) {
    parser = XML_ParserCreate(NULL);
    if (parser != NULL) {
        XML_SetUserData(parser, this);
        XML_SetElementHandler(parser,
            xml_startElementHandler,
            xml_endElementHandler);
        XML_SetCharacterDataHandler(parser, xml_characterDataHandler);
        buffer  = new char[MAX_BUFFER_LEN];
        buffer2 = new char[MAX_BUFFER_LEN];
    }
    stack.clear();
    coordinates.clear();
    coordinates_index    = 0;
    loopCoordinates_flag = false;
    verbose              = 0;
}

/**
 ****************************************************************************
 * @brief  Destructor
 ****************************************************************************
**/
KmlReader::~KmlReader() {
    delete []buffer2;
    buffer2 = NULL;

    delete []buffer;
    buffer = NULL;

    if (parser != NULL) {
        XML_ParserFree(parser);
        parser = NULL;
    }
}

/**
 ****************************************************************************
 * @brief  Reads in and parses an KML file.
 *
 * @param  filename  The name of the KML file
 ****************************************************************************
**/
bool KmlReader::parse(const std::string &filename) {
    bool flag = false;

    if ((parser == NULL) || (buffer == NULL)) {
        fprintf(stderr,
            "KmlReader.parse: Parser not created, could not parse \"%s\"\n",
            filename.c_str());
    } else {
        stack.clear();
        coordinates.clear();
        coordinates_index = 0;
        buffer2len = 0;
        FILE *fp = fopen(filename.c_str(), "rt");
        if (fp == NULL) {
            fprintf(stderr,
                "KmlReader.parse Failed to open \"%s\" for reading\n",
                filename.c_str());
        } else {
            printf("KmlReader.parse Opened \"%s\" for reading\n",
                filename.c_str());
            int done = 0;
            flag = true;
            do {
                int len = fread(buffer, 1, MAX_BUFFER_LEN, fp);
                done = (len < MAX_BUFFER_LEN);
                if (XML_Parse(parser, buffer, len, done) == XML_STATUS_ERROR) {
                    fprintf(stderr,
                        "KmlReader.parse: %s at line %" XML_FMT_INT_MOD
                        "u of \"%s\"\n",
                        XML_ErrorString(XML_GetErrorCode(parser)),
                        XML_GetCurrentLineNumber(parser),
                        filename);
                    flag = false;
                    done = 2;
                }
            } while (!done);

            fclose(fp);
            fp = NULL;
        }
    }

    return flag;
}

/**
 ****************************************************************************
 * @brief  Handles the start element tag.
 *
 * @param  name  The name of the element being opened
 * @param  atts  A paired array of attributes associated with the element
 ****************************************************************************
**/
void KmlReader::startElementHandler(const char *name, const char **atts) {
    if (verbose > 0) {
        printf("KmlReader ");
        for (int i = 0; i < static_cast<int> (stack.size()); i++) {
            printf("  ");
        }
        printf("<%s", name);
        for (int i = 0; atts[i] != NULL; i += 2) {
            printf(" %s='%s'", atts[i], atts[i + 1]);
        }
        printf(">\n");
    }
    stack.push_back(name);
    buffer2len = 0;
}

/**
 ****************************************************************************
 * @brief  Handles the end element tag.
 *
 * @param  name  The name of the element being closed
 ****************************************************************************
**/
void KmlReader::endElementHandler(const char *name) {
    if (name != stack.back()) {
        fprintf(stderr,
            "KmlReader.endElementHandler: Closing element tag '%s' "
            "does not match open element '%s'\n",
            name, stack.back().c_str());
    }
    stack.pop_back();
    buffer2len = 0;
    if (verbose > 0) {
        printf("KmlReader ");
        for (int i = 0; i < static_cast<int> (stack.size()); i++) {
            printf("  ");
        }
        printf("</%s>\n", name);
    }
}

/**
 ****************************************************************************
 * @brief  Handles ascii data between element tags.
 *
 * @param  s   The ascii data, unterminated
 * @param len  Numbers of characters of ascii data
 ****************************************************************************
**/
void KmlReader::characterDataHandler(const char *s, int len) {

    // append character data to buffer2
    int len2 = MAX_BUFFER_LEN - buffer2len;
    if (len2 > len) {
        len2 = len;
    }
    memcpy(&buffer2[buffer2len], s, len2);
    buffer2len += len2;

    // check for line end
    for (int i = 0; i < buffer2len; i++) {
        if ((buffer2[i] == '\n') || (buffer2[i] == '\r')) {
            buffer2[i] = '\0';
            i++;

            // process line
            if (i > 1) {
                lineHandler(buffer2);
            }
            if ((i < buffer2len) ||
                (buffer2[i] == '\n') ||
                (buffer2[i] == '\r')) {
                i++;
            }
            buffer2len -= i;
            if (buffer2len > 0) {
                memmove(buffer2, &buffer2[i], buffer2len);
            }
        }
    }
    if (verbose > 0) {
        if (s[0] == '\n') {
            s++;
            len--;
        }
        if (s[0] == '\r') {
            s++;
            len--;
        }
        if (len > 0) {
            printf("KmlReader \"");
            fwrite(s, 1, len, stdout);
            printf("\"\n");
        }
    }
}

/**
 ****************************************************************************
 * @brief  Handles a line of text within the current element context.
 *
 * @param  line  The ascii text, null terminated
 ****************************************************************************
**/
void KmlReader::lineHandler(const char *line) {
    if (stack.back() == "coordinates") {
        Coordinate coordinate;
        coordinate.latitude_degN  = 0.0;
        coordinate.longitude_degE = 0.0;
        coordinate.altitude_m     = 0.0;
        coordinate.altitudeValid_flag = false;
        int n = sscanf(line, " %lf , %lf , %lf",
                       &coordinate.longitude_degE,
                       &coordinate.latitude_degN,
                       &coordinate.altitude_m);
        if (n == 3) {
            coordinate.altitudeValid_flag = true;
        }
        coordinates.push_back(coordinate);
    }
}

/**
 ****************************************************************************
 * @brief  Retrieves the next coordinate from the coordinate list.
 *
 * @param  latitude_degN   Latitude in decimal degrees North of the equator
 * @param  longitude_degE  Longitude in decimal degrees East of the Prime Meridian
 * @param  altitude_m      Height above mean sea level in meters
 * @param  altitudeValid_flag  True if altitude parameter contains a valid value
 ****************************************************************************
**/
bool KmlReader::getNextCoordinate(double *latitude_degN,
                                  double *longitude_degE,
                                  double *altitude_m,
                                  bool   *altitudeValid_flag) {
    bool flag = true;

    if (loopCoordinates_flag && (coordinates_index >= coordinates.size())) {
        coordinates_index  = 0;
    }
    if (coordinates_index < coordinates.size()) {
        *latitude_degN      = coordinates[coordinates_index].latitude_degN;
        *longitude_degE     = coordinates[coordinates_index].longitude_degE;
        *altitude_m         = coordinates[coordinates_index].altitude_m;
        *altitudeValid_flag = coordinates[coordinates_index].altitudeValid_flag;
        coordinates_index++;
    } else {
        flag = false;
    }

    return flag;
}

/**
 ****************************************************************************
 * @brief  Self test method.
 ****************************************************************************
**/
bool KmlReader::unitTest() {
    bool flag = false;

    kmlio::KmlReader kmlReader;
    flag = kmlReader.parse("example.kml");
    bool continue_flag = true;
    while (continue_flag) {
        double latitude_degN  = 0.0;
        double longitude_degE = 0.0;
        double altitude_m     = 0.0;
        bool   altitudeValid_flag = false;
        continue_flag = kmlReader.getNextCoordinate(
            &latitude_degN, &longitude_degE, &altitude_m, &altitudeValid_flag);
        if (continue_flag) {
            if (altitudeValid_flag) {
                printf("lat/lon: %.6lf/%.6lf deg;  alt: %.1f m\n",
                    latitude_degN, longitude_degE, altitude_m);
            } else {
                printf("lat/lon: %.6lf/%.6lf deg\n",
                    latitude_degN, longitude_degE);
            }
        }
    }

    printf("KmlReader.unitTest %s\n", (flag) ? "Passed" : "Failed");

    return flag;
}
